import re

from django.db.models import Q
from django.db.models.query import QuerySet
from django import forms
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger, InvalidPage
from django.utils.text import smart_split
from django.conf import settings
from django.core.exceptions import FieldError
from django.conf import settings

class BaseSearchForm(forms.Form):
    DEFAULT_OPERATOR = Q.__and__
    q = forms.CharField(label='Search', required=False)
    
    class Meta:
        abstract = True
        base_qs = None
        search_fields = None
        default_fields = {}
        
    def clean_q(self):
        return self.cleaned_data['q'].strip()
        
    def construct_search(self, field_name, first):
        if field_name.startswith('^'):
            if first:
                return "%s__istartswith" % field_name[1:]
            else:
                return "%s__icontains" % field_name[1:]
        elif field_name.startswith('='):
            return "%s__iexact" % field_name[1:]
        elif field_name.startswith('!'):
            return "%s" % field_name[1:]
        else:
            return "%s__icontains" % field_name
    
    def get_text_search_query(self, query_string):
        filters, first = [], True
        
        for (q, v) in self.Meta.default_fields.items():
            filters.append(Q(**{self.construct_search(str(q), first): v}))
        
        for bit in list(smart_split(query_string)):
            or_queries = [Q(**{self.construct_search(str(field_name), first): bit}) for field_name in self.Meta.search_fields]
            filters.append(reduce(Q.__or__, or_queries))
            first = False
        if len(filters):
            return reduce(self.DEFAULT_OPERATOR, filters)
        else:
            return False

    def construct_filter_args(self, cleaned_data):
        args = []
                
        for field in cleaned_data:
            if hasattr(self, 'prepare_%s' % field):
                q_obj = getattr(self, 'prepare_%s' % field)()
                if q_obj:
                    args.append(q_obj)
            elif isinstance(cleaned_data[field], Q):
                args.append(cleaned_data[field])
            elif cleaned_data[field]:
                if isinstance(cleaned_data[field], list) or isinstance(cleaned_data[field], QuerySet):
                    args.append(Q(**{field + '__in': cleaned_data[field]}))
                else:
                    args.append(Q(**{field: cleaned_data[field]}))
        return args
    
    def get_result_queryset(self, objects=None):
        qs = objects if objects else self.Meta.base_qs

        if not self.is_bound:
            return qs.all()
        if not self.is_valid():
            return []
        if not self.Meta.search_fields:
            return qs.all()
        cleaned_data = self.cleaned_data.copy()
        
        from brispon.models import Keyword
        term = cleaned_data.pop('q')
        if term:
            Keyword.objects.add_or_update(term=term)
        
        search_query = self.get_text_search_query(term)
        
#        qs = qs.filter(search_query) if search_query else qs.none() 
        if search_query:
            qs = qs.filter(search_query)            
        qs = qs.filter(*self.construct_filter_args(cleaned_data))
        return qs
    
    
class URLArgument(object):
    def __init__(self, arguments):
        self.arguments = arguments

    def urlencode(self):
        arguments = self.arguments.copy()
        for k,v in arguments.iteritems():
            if not v:
                arguments.pop(k)
        return arguments.urlencode()

    def encode_without(self, key):
        arguments = self.arguments.copy()
        if key in arguments:
            arguments.pop(key)
        return arguments.urlencode()

    def encode_without_page(self):
        return self.encode_without('page')

    def encode_without_order(self):
        return self.encode_without('order')

    
def paginate(arguments, search_form=None, objects=None, unit_page=15):
    if 'order' in arguments:
        objects = objects.order_by(arguments['order'])
    if search_form:
        objects = search_form.get_result_queryset(objects)
    number_of_records = 0
    if objects:
        if isinstance(objects, QuerySet):
            number_of_records = objects.count()
        else:
            number_of_records = len(objects)

    paginator = Paginator(objects, unit_page)
    try:
        page_num = int(arguments.get('page', '1'))
    except ValueError:
        page_num = 1

    try:
        objects_page = paginator.page(page_num)
    except (EmptyPage, InvalidPage):
        objects_page = None
    adjacent_pages = 2
    page_numbers = [n for n in \
                    range(page_num - adjacent_pages, page_num + adjacent_pages + 1) \
                    if n > 0 and n <= paginator.num_pages]
    return {
        'page_num': page_num,
        'objects_page': objects_page,
        'number_of_records': number_of_records,
        'url_arguments': URLArgument(arguments),
        'search_form': search_form,

        'page_numbers': page_numbers,
        'next': objects_page.next_page_number(),
        'previous': objects_page.previous_page_number(),
        'has_next': objects_page.has_next(),
        'has_previous': objects_page.has_previous,
        'show_first': 1 not in page_numbers,
        'show_last': paginator.num_pages not in page_numbers,
        'last_page': paginator.num_pages,
        }
    
def init_inc_paginate(arguments, search_form=None):
        return {
        'url_arguments': URLArgument(arguments),
        'search_form': search_form,
        }    
    
def inc_paginate(arguments, search_form=None, objects=None, unit_page=9):
    if not objects and search_form:
        objects = search_form.get_result_queryset(objects)
    get_page = arguments.get('page', 1)
    paginator = Paginator(objects, unit_page)
    try:
        page = paginator.page(get_page)
        objects = page.object_list
    except PageNotAnInteger:
        page = paginator.page(1)
        objects = page.object_list
    except EmptyPage:
        objects = None
    return {
            'objects': objects,
            }

def search(arguments, search_form=None, objects=None):
    return search_form.get_result_queryset()
        